﻿using System;

using PostSharp.Aspects;
using BizElements.BusinessLayer;

namespace BizElements.BusinessLayer.Aspects
{
    #region Documentation.
    /// <summary>
    /// Marks a method which is modifying data which may be present in <see cref="GlobalCache"/> or <see cref="UserCache"/>.
    /// </summary>
    /// <remarks><para>During the build process the code, which automatically removes/invalidates global cache categories related to current object,
    /// is automatically injected and executed after the method marked with <b>InvalidatesCacheAttribute</b> attribute is exited.</para>
    /// <para>To use this attribute you have to install PostSharp 2 on your development computer and add references to <b>PostSharp.dll</b> assembly to your project.</para>
    /// </remarks>
    /// <example>The following example demonstrates how to use the attribute:
    /// <code>
    /// [InvalidatesCache]
    /// public void Save()
    /// {
    ///     DAL.GetDao(this).Save();
    /// }
    /// </code>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public void Save()
    /// {
    ///     DAL.GetDao(this).Save();
    ///     GlobalCache.RemoveCategory("Countries");
    /// }
    /// </code>
    /// <para><b>RemoteableAttribute</b> and <b>InvalidatesCacheAttribute</b> can be used together. <b>InvalidatesCacheAttribute</b> has higher aspect priority, 
    /// thus it is evaluated before the <b>RemoteableAttribute</b> ensuring that the injected code is executed in both local and remote domain. 
    /// The following example demonstrates how the attributes are combined:
    /// <code>
    /// [Remoteable]
    /// [InvalidatesCache]
    /// public virtual void Save()
    /// {
    ///     DAL.GetDao(this).Save();
    /// }
    /// </code>
    /// </para>
    /// <para>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public void Save()
    /// {
    ///     RpcHelper.InvokeMethod(this, DbSave);
    ///     GlobalCache.RemoveCategory("Countries");
    /// }
    /// 
    /// protected virtual void DbSave()
    /// {
    ///     DAL.GetDao(this).Save();
    ///     GlobalCache.RemoveCategory("Countries");
    /// }
    /// </code>
    /// </para>
    /// </example>
    #endregion
    [Serializable]
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class InvalidatesCacheAttribute : OnMethodBoundaryAspect
    {
        /// <summary>
        /// Initializes a new instance of <b>InvalidatesCacheAttribute</b> class and sets its <b>AspectPriority</b>.
        /// </summary>
        public InvalidatesCacheAttribute()
        {
            this.AspectPriority = 1;
        }

        /// <summary>
        /// Removes/invalidates global cache categories related to current object. Executed <b>after</b> the body of methods to which this aspect is applied. 
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed, which are its arguments, and how should the execution continue after the execution of <b>OnExit</b>.</param>
        public sealed override void OnExit(MethodExecutionArgs eventArgs)
        {
            string[] globalCategories = GetCategories(eventArgs);
            if (!ArrayUtil.IsNullOrEmpty(globalCategories))
                GlobalCache.RemoveCategories(globalCategories);
        }

        private string[] GetCategories(MethodExecutionArgs eventArgs)
        {
            if (ArrayUtil.IsNullOrEmpty(this.Categories))
                return CacheAspectConfiguration.CategoryGenerator.GetCategories(eventArgs);
            else
                return this.Categories;
        }

        /// <summary>
        /// Gets or sets manually defined global cache categories which are to be removed, ie. invalidated.
        /// </summary>
        /// <value>Array of strings. Default is <b>null</b> indicating that the categories will be automatically assigned using the configured <see cref="ICacheCategoryGenerator"/> object.</value>
        public string[] Categories { get; set; }
    }
}
